﻿/// <summary>
///     Right hand rule description of a replication rule
/// </summary>

using md.imi.membrane.common.Messaging;
using md.imi.membranes.objects.alphabet;
using md.imi.membranes.objects.membranes;
using System.Collections.Generic;

namespace md.imi.membranes.objects.rules
{
    using System;
    using md.imi.membrane.constants;
    using md.imi.membranes.objects.strings;

    /// <summary>
    ///     Right hand rule implementation
    /// </summary>
    [Serializable]
    public class RightHandRule : Rule
    {
        /// <summary>
        ///     Gets or Sets the resutling string object (the processing result, the string used to replace found one)
        /// </summary>
        public StringObject Result { get; set; }

        /// <summary>
        ///     Gets or Set the type of the rule - in, out , here:
        ///     in      - the label of the target memebrane is indicated (applies only for immediately nested membrane whose label is indicated in TargetMembraneLabel)
        ///     out     - the atrget memebrane is calculated (parent membrane))
        ///     here    - content remains in the same membrane
        /// </summary>
        public RuleTargets TargetType { get; set; }

        /// <summary>
        /// Gets / Sets the label of the membrane which is modified. Not a mandatory field.
        ///     The rule can be applied to all membranes simultaneosly with the same label.
        /// The rule is applied to the near iner membrane
        /// </summary>
        public String TargetMembraneLabel { get; set; }

        /// <summary>
        ///     To string
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return String.Format("{0} {1} {2} ", Result != null ? Result.ToString() :  "", 
                TargetType != null ?  TargetType.ToString().ToLower() : "", 
                TargetMembraneLabel != null ? TargetMembraneLabel : "");
            // return Label + " " + TargetType + ":  " + TargetMembraneLabel + " " + Result;
        }

        #region Constructors
        public RightHandRule (){}

        /// <summary>
        ///     Copy constructor 
        /// </summary>
        /// <param name="rule"></param>
        public RightHandRule (RightHandRule rule) : base(rule)
        {
            this.Result = new StringObject(rule.Result);
            this.TargetType = rule.TargetType;
            this.TargetMembraneLabel = rule.TargetMembraneLabel;
        }
        #endregion
        
        public override Rule GetCopy ()
        {
            var newRule = new RightHandRule(this);
            return newRule;
        }
        
        /// <summary>
        ///    Perform replacement according to solution
        /// </summary>
        /// <param name="solution"></param>
        /// <returns></returns>
        private StringObject GenerateStringObject(Solution solution)
        {
            var so = new StringObject(solution.ProcessedString);

            so.RemoveAt(solution.ProcessedSubStringIndex);
            so.InsertRange(solution.ProcessedSubStringIndex, this.Result);
            return so;
        }

        /// <summary>
        ///     Apply right hand rule
        /// </summary>
        /// <param name="solution"></param>
        /// <param name="newMembrane"></param>
        /// <param name="parrentConfig"></param>
        public override void ApplyRule(Solution solution, psystems.PSystemsTree parentConfig)
        {
            MembranesTree targetMembrane = MembranesTree.SearchMembraneById(parentConfig.Membranes.Membranes, solution.Membrane.Id);
            if (targetMembrane == null)
            {
                return;
            }

            switch (this.TargetType)
            {
                // add resulting string to target membrane
                case RuleTargets.Here:
                    {
                        var so = GenerateStringObject(solution);
                        if (targetMembrane.IsEnvironment() && so.IsTerminal())
                        {
                            WorkSpace.Instance.MessagesManager.AddMessage(
                                new MessageData(solution, DestinationType.GeneratedLanguageTarget, so));
                        }
                        targetMembrane.Multiset.Add(so);
                        break;
                    }

                // search for target membrane, and add result to the membrane
                case RuleTargets.In:
                    {
                        foreach (var m in targetMembrane.Membranes)
                        {
                            if (m.Label == this.TargetMembraneLabel)
                            {
                                /*
                                // A inner membrane cannot be environment
                                var so = GenerateStringObject(solution);
                                if (m.IsEnvironment())
                                {
                                    WorkSpace.Instance.MessagesManager.AddMessage(
                                        new MessageData(solution, typeof(MessageData.GeneratedLanguageTarget), so));
                                }
                                */
                                m.Multiset.Add(GenerateStringObject(solution));
                                break;

                            }
                        }

                        break;
                    }

                // get parent membrane, and add result to it's multiset
                // it is supposed that the whole P system is dublicated, and the 
                // parent membrane is a new one too. So, get it and insert data to it
                case RuleTargets.Out:
                    {
                        var parentMembrane = parentConfig.GetMembraneParent(targetMembrane);
                        if (parentMembrane == null)
                        {
                            Console.WriteLine("Could not get paretn of new membrane : {0}",
                                              targetMembrane.ToString());
                            return;
                        }
                    
                        if (parentMembrane.Multiset == null)
                        {
                            parentMembrane.Multiset = new List<StringObject> ();
                        }

                        var so = GenerateStringObject(solution);
                        if (parentMembrane.IsEnvironment() && so.IsTerminal())
                        {
                            WorkSpace.Instance.MessagesManager.AddMessage(
                                new MessageData(solution, DestinationType.GeneratedLanguageTarget, so));
                        }

                        parentMembrane.Multiset.Add(so);
                        break;
                    }
            }
        }

    }
}